// 文件：src/ch08/react-agent.ts
// import { ChatOpenAI } from "@langchain/openai";
import { ChatOllama } from '@langchain/ollama';
import { PromptTemplate } from "@langchain/core/prompts";
import { ToolManager } from "./tool-manager";
import { fromTemplate } from "./FromTemplate";
export type AgentState = {
  question: string;
  thought: string;
  action: string;
  actionInput: Record<string, unknown>;
  observation: string;
  answer: string;
  step: number;
};

export class ReActAgent {
  private llm: ChatOllama;
  private tools: ToolManager;
  private maxSteps = 5;

  constructor() {
    this.llm = new ChatOllama({
      baseUrl: 'http://192.168.31.159:11434',
      model: 'llama3.1:latest', // 使用您想要的模型
    });
    this.tools = new ToolManager();
  }

  private getPrompt() {
    return PromptTemplate.fromTemplate(fromTemplate);
  }

  async run(
    question: string,
    onStream?: (chunk: string) => void
  ): Promise<AgentState[]> {
    const steps: AgentState[] = [];// 记录每一步的状态
    let currentThought = "";// 维护上下文
    let step = 0;// 当前步骤计数

    while (step < this.maxSteps && !finalAnswer) {
      // 循环的核心是让 LLM 基于上下文思考，决定是否调用工具或直接生成答案。
      // 获取你定义的提示词模板
      const prompt = this.getPrompt();
      // 让 LLM 知道 "有哪些工具可用""该怎么用工具"，相当于给 LLM 递上 "工具说明书"。
      // 将所有工具的name和description整理为字符串，告知 LLM 可用工具
      const toolsDesc = this.tools.getAllTools().map(t => `${t.name}: ${t.description}`).join("\n");
      // console.log(toolsDesc)
      
      let content = "";
      
      // 如果有流式回调，使用 stream 方法
      if (onStream) {
        const stream = await prompt.pipe(this.llm).stream({
          question,
          tools: toolsDesc,
          thought: currentThought || ""
        });
        
        for await (const chunk of stream) {
          const text = chunk.content as string;
          content += text;
          onStream(text); // 实时发送每个 chunk
        }
      } else {
        // 没有回调，使用原来的 invoke 方法
        const response = await prompt.pipe(this.llm).invoke({
          question,
          tools: toolsDesc,
          thought: currentThought || ""
        });
        content = response.content as string;
      }
      // console.log(content)

      // 解析响应（简化版，实际需要更复杂的解析）
      // const thoughtMatch = content.match(/思考：(.*?)(?=\n|$)/);
      // const actionMatch = content.match(/行动：(.*?)(?=\n|$)/);
      // const inputMatch = content.match(/行动输入：(.*?)(?=\n|$)/);
      // const answerMatch = content.match(/最终答案：(.*?)(?=\n|$)/);

      // const thoughtMatch = content.match(/思考\s*：\s*(.*?)(?=\n|$)/);
      // const actionMatch = content.match(/行动\s*：\s*(.*?)(?=\n|$)/);
      // const inputMatch = content.match(/行动输入\s*：\s*(.*?)(?=\n|$)/);
      // const answerMatch = content.match(/最终答案\s*：\s*(.*?)(?=\n|$)/);

      // 使用正则表达式提取结构化信息
      const thoughtMatches = content.match(/思考\s*[：:]\s*(.*?)(?=\n|$)/g);
      const thoughtMatch = thoughtMatches ? thoughtMatches[thoughtMatches.length - 1].match(/思考\s*[：:]\s*(.*?)(?=\n|$)/) : null;

      // 提取行动、行动输入、最终答案
      // 匹配第一个行动（如果LLM输出了多个，只处理第一个）
      const actionMatch = content.match(/行动\s*[：:]\s*(.*?)(?=\n|$)/); //比如 行动：getCurrentWeather
      // 匹配紧跟在行动后面的行动输入（如果有的话）
      const inputMatch = content.match(/行动输入\s*[：:]\s*(.*?)(?=\n|$)/);
      const answerMatch = content.match(/最终答案\s*[：:]\s*(.*?)(?=\n|$)/);


      // 优先检查是否有行动需要执行（即使没有行动输入，也要执行工具）
      if (actionMatch) {
        // 清理工具名：移除尖括号、空格等特殊字符
        let action = actionMatch[1].trim();
        // 移除可能的尖括号 < >  避免因格式问题找不到工具
        action = action.replace(/[<>]/g, '').trim();

        // 如果有行动输入，解析它；否则使用空对象（适用于无参数工具如 get_time）
        let actionInput = {};
        if (inputMatch) {// 如果 LLM 输出了“行动输入”
          // 复杂的JSON解析逻辑，支持多种格式
          // 包括自动修复无引号的JSON等问题
          const inputStr = inputMatch[1].trim();
          // 如果输入是"无"、"无参数"、"null"等，使用空对象
          if (inputStr.toLowerCase() === '无' ||
            inputStr.toLowerCase() === '无参数' ||
            inputStr.toLowerCase() === 'none' ||
            inputStr.toLowerCase() === 'null' ||
            inputStr === '{}' ||
            inputStr === '') {
            actionInput = {};// 明确无参数，用空对象
          } else {
            try {
              // 尝试修复常见的JSON格式问题（如 {key: "value} 应该是 {"key": "value"}）
              let fixedInputStr = inputStr.trim();
              // 如果输入看起来像对象但没有引号，尝试修复
              if (fixedInputStr.startsWith('{') && fixedInputStr.endsWith('}')) {
                // 尝试给键名加引号（如果键名没有引号）
                // 匹配 key: value 模式，但跳过已经有引号的键
                fixedInputStr = fixedInputStr.replace(/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g, '$1"$2":');
              }
              actionInput = JSON.parse(fixedInputStr);
            } catch {
              // 如果解析失败，尝试提取键值对
              try {
                // 尝试匹配 expression: "value" 或 expression:"value" 格式
                const keyValueMatch = inputStr.match(/(\w+)\s*[：:]\s*["']?([^"']+)["']?/);
                if (keyValueMatch) {
                  actionInput = { [keyValueMatch[1]]: keyValueMatch[2] };
                } else {
                  // 如果还是失败，使用空对象
                  actionInput = {};
                }
              } catch {
                // 如果还是失败，使用空对象
                actionInput = {};
              }
            }
          }
        }

        // 解决LLM可能用错参数名的问题，并进行校验
        actionInput = this.tools.validateAndPreprocess(action, actionInput);

        // 验证工具是否存在
        const tool = this.tools.getTool(action);
        if (!tool) {
          // 工具不存在，将错误信息作为观察结果，让 LLM 重新思考
          const observation = `错误：工具 "${action}" 不存在。可用工具列表：${this.tools.getAllTools().map(t => t.name).join(', ')}。请检查工具名是否正确，或者如果问题不需要工具，直接给出最终答案。`;
          steps.push({
            question,
            thought: thoughtMatch?.[1] || "",
            action,
            actionInput,
            observation,
            answer: "",
            step
          });

          // 将错误信息添加到上下文中，让 LLM 重新思考
          currentThought += `\n思考：${thoughtMatch?.[1] || ""}\n行动：${action}\n行动输入：${JSON.stringify(actionInput)}\n观察：${observation}\n`;
          step++;
          continue;
        }

        // 执行工具（调用 ToolManager 的 execute 方法，触发工具的 _call 逻辑）
        const observation = await this.tools.execute(action, actionInput);

        steps.push({
          question,
          thought: thoughtMatch?.[1] || "",
          action,
          actionInput,
          observation,
          answer: "",
          step
        });

        // 如果是数据分析工具，直接基于观察生成最终答案，避免重复调用导致没有终止
        try {
          const obsStr = typeof observation === 'string' ? observation : JSON.stringify(observation);
          if (action === 'analyze_data') {
            const clean = obsStr.replace(/^执行结果：\n?/, '');
            steps[steps.length - 1].answer = `统计摘要如下：\n${clean}`;
            break;
          }
          // 通用重复调用保护：同一工具已调用 >= 3 次，则基于最近观察结束
          const sameCount = steps.filter(s => s.action === action).length;
          if (sameCount >= 3) {
            steps[steps.length - 1].answer = `根据观察结果：\n${obsStr}`;
            break;
          }
        } catch { /* 忽略格式化错误，继续常规流程 */ }

        // 重要：当本轮存在“行动”时，忽略本轮模型输出的“最终答案”。
        // 让观察结果进入上下文，强制下一轮再生成最终答案，避免在没有真实观察的情况下幻觉。

        // 检查是否所有任务都已完成
        // 检测重复执行：如果最近3步中有重复的工具调用，可能是在循环
        const recentActions = steps.slice(-3).map(s => s.action).filter(a => a);
        const hasRepeatedAction = recentActions.length >= 2 &&
          recentActions[recentActions.length - 1] === recentActions[recentActions.length - 2];

        // 检测是否已经执行了多个不同的工具（可能表示任务已完成）
        // 注意：这仅适用于需要多个工具的问题，对于单个问题或不需要工具的问题，不应该触发
        const uniqueActions = new Set(steps.map(s => s.action).filter(a => a));

        // 如果检测到重复执行，强制要求给出答案（避免无限循环）
        // 但只有在确实执行了工具的情况下才触发（避免对不需要工具的问题误判）
        if (hasRepeatedAction && step >= 2 && uniqueActions.size > 0) {
          // 强制要求 LLM 给出最终答案
          currentThought += `\n思考：${thoughtMatch?.[1] || ""}\n行动：${action}\n行动输入：${JSON.stringify(actionInput)}\n观察：${observation}\n`;
          currentThought += `\n\n⚠️ 重要提示：检测到重复执行工具，所有任务似乎都已完成。请立即给出最终答案，不要再调用任何工具！直接输出"最终答案："格式。\n`;
          step++;
          continue;
        }

        // 将执行结果添加到上下文中，供下一轮使用
        // 注意：这里使用真实的观察结果，而不是模型编造的
        currentThought += `\n思考：${thoughtMatch?.[1] || ""}\n行动：${action}\n行动输入：${JSON.stringify(actionInput)}\n观察：${observation}\n`;
        step++;//进入下一轮循环
        continue; // 继续下一轮循环
      }

      // 如果没有行动，但有最终答案，直接返回
      if (answerMatch) {
        // 当本轮没有行动而出现最终答案时，优先做一致性纠偏：
        // 如果上一轮是 getCurrentWeather，并且观察是结构化 JSON，就基于观察生成权威答案，避免幻觉。
        const lastStep = steps[steps.length - 1];
        let finalAnswer = answerMatch[1];
        try {
          if (lastStep && lastStep.action === 'getCurrentWeather' && typeof lastStep.observation === 'string') {
            const obsStr = lastStep.observation.trim();
            if (obsStr.startsWith('{') && obsStr.endsWith('}')) {
              const obs = JSON.parse(obsStr);
              // 构造严格基于观察的答案（不引用模型臆测）
              finalAnswer = `根据观察结果：天气状况为${obs.text}，温度为${obs.temp}，湿度为${obs.humidity}，风向为${obs.windDir}。更新时间：${obs.updateTime}。`;
            }
          }
        } catch { /* 忽略解析错误，保留模型答案 */ }

        steps.push({
          question,
          thought: thoughtMatch?.[1] || "",
          action: "",
          actionInput: {},
          observation: "",
          answer: finalAnswer,
          step
        });
        break;
      }

      // 如果既没有行动也没有答案，检查是否应该强制要求给出答案
      if (steps.length >= 2) {
        // 如果已经执行了多个步骤，强制要求给出答案
        currentThought += `\n重要提示：所有任务似乎都已完成，请立即给出最终答案！\n`;
        step++;
        continue;
      }

      // 如果既没有行动也没有答案，退出循环
      break;
    }

    return steps;
  }
}
